import java.awt.Component;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.LinkedList;
import java.util.Random;
import java.util.Scanner;

import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;


//TODO 

public class CardStack extends JPanel {
	
	private boolean revealed;
	private int numb;
	private LinkedList<Card> cards;
	private final Card back;
	private Card top;
	private final Card empty;
	private JLabel numbDis;
	private String name;
	private CardStack thisStack = this;
	private Random rand;
	
	
	/**
	 * Constructor for an empty CardStack
	 * @param the name of the Stack
	 */
	public CardStack(String pName){
		this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
		name = pName;
		revealed = false;
		numb = 0;
		cards = new LinkedList<Card>();
		back = new Card(Card.backside);
		top = back;
		empty = new Card(Card.empty);
		numbDis = new JLabel();
		PopClickListener pl = new PopClickListener(this);
		addMouseListener(pl);
		addMouseMotionListener(pl);
		this.add(numbDis);
		this.add(top);
		this.resetGraphic();
		rand = new Random();
	}
	
	/**
	 * returns the name of this CardStack
	 * @return this CardStacks's name
	 */
	public String getName(){
		return name;
	}
	
	/**
	 * gives the number of cards in this CardStack
	 * @return the number of cards in this CardStack
	 */
	public int getNumberOfCards(){
		return numb;
	}
	
	/**
	 * creates a new CardStack with a givven Name and containing all cards named in pSrc
	 * @param pName the name of the new CardStack
	 * @param pSrc the name of the textfile, containing all the cards for this CardStack
	 */
	public CardStack(String pName, String pSrc){
		this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
		name = pName;
		revealed = false;
		numb = 0;
		cards = new LinkedList<Card>();
		back = new Card(Card.backside);
		top = back;
		empty = new Card(Card.empty);
		numbDis = new JLabel();
		PopClickListener pl = new PopClickListener(this);
		addMouseListener(pl);
		addMouseMotionListener(pl);
		try{
			Scanner sc;
			if(pSrc.contains(".txt")){
				sc = new Scanner(new File("rsc/"+pSrc));
			}
			else{
				sc = new Scanner(pSrc);
				sc.useDelimiter(" ");
			}
			while(sc.hasNext()){
				String card = sc.next();
				int a = 1;
				if(sc.hasNextInt()){
					a = sc.nextInt();
				}
				for(int i = 0;i<a;i++){
					insertOnTop(new Card(card));
				}
			}
		}
		catch (FileNotFoundException e){
			System.out.println("Did not find your src file. Created emtpy CardStack.");
		}
		this.add(numbDis);
		this.add(top);
		this.resetGraphic();
		rand = new Random();
	}
	
	public void fillDeck(String pSrc){
		try{
			Scanner sc = new Scanner(new File("rsc/"+pSrc));
			while(sc.hasNext()){
				String card = sc.next();
				int a = 1;
				if(sc.hasNextInt()){
					a = sc.nextInt();
				}
				for(int i = 0;i<a;i++){
					insertOnTop(new Card(card));
				}
			}
		}
		catch (FileNotFoundException e){
			System.out.println("Did not find your src file. Created emtpy CardStack.");
		}
		this.resetGraphic();
	}
	
	
	/**
	 * updates the cardStacks graphics
	 */
	public void resetGraphic(){
		numbDis.setText(name+ ": "+Integer.toString(numb));
		//display top card
		if(revealed && numb > 0){
			this.remove(top);
			this.validate();
			top = cards.getFirst();
			this.add(top);
			this.validate();
			//System.out.println("erster Zweig");
		}
		//display backside
		else if(numb > 0){
			//System.out.println("drin");
			//System.out.println(top.toString());
			this.remove(top);
			this.validate();
			top = back;
			this.add(top);
			this.validate();
			//System.out.println("zweiterZweig");
		}
		//display empty Stack
		else{
			this.remove(top);
			this.validate();
			top = empty;
			this.add(top);
			this.validate();
		}
		thisStack.validateTree();
		thisStack.repaint();
		Game.getGame().gui.pack();
	}
	
	/**
	 * sets the number of cards to given integer
	 * 
	 * (never used, because the it has no effect on the actual number of cards in the stack)
	 * @param the number of cards
	 */
	private void setNumb(int pI){
		if(pI >= 0){
			numb = pI;
			this.resetGraphic();
		}
	}
	
	/**
	 * reduces the number of cards by one
	 */
	private void decNumb(){
		if(numb > 0){
			--numb;
			this.resetGraphic();
		}
	}
	
	/**
	 * increases the number of cards by one
	 */
	private void incNumb(){
		++numb;
		this.resetGraphic();
	}
	
	
	/**
	 * shuffles the cardstack
	 */
	public void shuffle(){
		LinkedList<Card> save = (LinkedList<Card>)cards.clone();
		cards = new LinkedList<Card>();
		for(int i = 1; i <= numb; ++i){
			cards.add(  save.remove( (int)(Math.random()*(numb-i)) )  );
		}
		this.resetGraphic();
		StringBuffer msg = new StringBuffer("cardstack ").append(name).append(" is shuffeld\n");
		fireEvent(new ActionEvent(this,42,msg.toString()));
	}
	
	/**
	 * Rearranges the cards like the numbers in pIA
	 * @param pIA the new order of the cradStack
	 */
	public void shuffle(int[] pIA){
		if(pIA.length != numb){
			System.out.println("sync error!");
		}
		else{
			LinkedList<Card> save = (LinkedList<Card>)cards.clone();
			cards = new LinkedList<Card>();
			for(int i = 0; i < numb; ++i){
				cards.add(  save.remove( pIA[i] )  );
			}
			this.resetGraphic();
			//StringBuffer msg = new StringBuffer("cardstack ").append(name).append(" is shuffeld\n");
			//fireEvent(new ActionEvent(this,42,msg.toString()));
		}
	}
	
	/**
	 * creates an integerArray, which can be used to shuffle a Stack of its own size
	 * @return
	 */
	public int[] createShuffle(){
		int[] erg= new int[numb];
		for(int i = 0; i < numb; ++i){
			erg[i]=( (int)(rand.nextDouble()*(numb-i)) );
		}
		return erg;
	}
	
	/**
	 * draws a card from the CardStack
	 * 
	 * gives back the top card of this catdStack, removes it and updates the grapic. if the cardStack is empty, null is returned
	 * @return the top card, or null
	 */
	public Card draw(){
		Card erg = null;
		if(numb>0){
			erg = cards.removeFirst();
			this.decNumb();
		}
		StringBuffer msg = new StringBuffer("draws the top card from stack ").append(name).append("\n");
		fireEvent(new ActionEvent(this,42,msg.toString()));
		return erg; 
	}
	
	/**
	 * reveals the top cards of the cardStack
	 * 
	 * returns an array of cards, containing the the top cards of this cardStack. They are not removed from the cardStack. If you try to reveal more cards, then there are in the cradStack, just all cards are revealed
	 * @param the number of top cards to be revealed
	 * @return an array of cards, containing the top crads
	 */
	public Card[] revealTop_old(int pI){
		Card[] erg;
		if(pI <= numb){
			erg = new Card[pI];
		}
		else{
			erg = new Card[numb];
		}
		//wahrscheinlich schneller
		//Iterator<Card> help = cards.iterator();
		for(int i = 0; i<erg.length;i++){
			erg[i] = cards.get(i);
		}
		return erg;
	}
	
	
	/**
	 * reveals the top cards for all players to the log
	 * @param pI the number of cards to be revealed
	 */
	public void revealTop(int pI, String player){
		if(pI > numb){
			pI = numb;
		}
		//wahrscheinlich schneller
		//Iterator<Card> help = cards.iterator();
		StringBuffer msg = new StringBuffer(player + " revealed the top " +pI + " cards from " + name+ ": \n");
		for(int i = 0; i<pI;i++){
			msg.append(cards.get(i).getName()).append("\n");
		}
		Game.getGame().getLog().addLog(msg.toString());
	}
	
	/**
	 * inserts the card at the specified position in the cardStack
	 * 
	 * you can't sort Cards within a stack using this method
	 * @param pC the new card
	 * @param pI the new position within the cardStack
	 */
	public void insertAt(int pI, Card pC){
		cards.add(pI, pC);
		this.incNumb();
		this.resetGraphic();
	}
	
	/**
	 * put the card on top of the cardStack
	 * @param pC the new top card
	 */
	public void insertOnTop(Card pC){
		this.incNumb();
		cards.addFirst(pC);
		this.resetGraphic();
	}
	
	/**
	 * sets the state of revealed to the givven boolean
	 * @param pB the new state of revealed
	 */
	public void setRevealed(boolean pB){
		revealed = pB;
		this.resetGraphic();
	}
	
	/**
	 * switches the state of revealed and unreaveld
	 */
	public void switchRevealed(){
		revealed = !revealed;
		this.resetGraphic();
		fireEvent(new ActionEvent(this, 42, name +" is"+ (revealed?" ":" no longer ")  + "revealed \n"));
	}
	
	/**
	 * fires off the given event to all LogListeners
	 * 
	 * no longer used
	 * @param pAE
	 */
	private void fireEvent(ActionEvent pAE){
		LogListener[] help = this.getListeners(LogListener.class);
		for(int i=0;i<help.length;i++){
			help[i].actionPerformed(pAE);
		}
	}
	
	public void addLogListener(LogListener pLl){
		this.listenerList.add(LogListener.class, pLl);
	}
	
	
	class PopUpMenu extends JPopupMenu {
	    JMenuItem itemDraw,itemFlip, itemShuffle, itemReveal;
	    public PopUpMenu(CardStack pCS){
	        itemDraw = new JMenuItem("Draw");
	        itemDraw.addActionListener(new StackActionListener(pCS));
	        itemFlip = new JMenuItem("Flip");
	        itemFlip.addActionListener(new StackActionListener(pCS));
	        itemShuffle = new JMenuItem("Shuffle");
	        itemShuffle.addActionListener(new StackActionListener(pCS));
	        itemReveal = new JMenuItem("Reveal");
	        itemReveal.addActionListener(new StackActionListener(pCS));
	        add(itemDraw);
	        add(itemFlip);
	        add(itemShuffle);
	        add(itemReveal);
	    }
	}

	class PopClickListener implements MouseMotionListener, MouseListener {
		private Component moveComponent;
		boolean dragged;
		private int x, y;
		public PopClickListener(Component c) {
			moveComponent = c;
			dragged = false;
		}
	    public void mousePressed(MouseEvent e){	
	    	if (e.isPopupTrigger())
	    		doPop(e);
	    	else {
	    		dragged = true;
	    		x = e.getX();
	    		y = e.getY();
	    	}
	    }

	    public void mouseReleased(MouseEvent e){
	        if (e.isPopupTrigger())
	            doPop(e);
	        Point p = SwingUtilities.convertPoint(thisStack, x, y, Game.getGame().cardtable);
	        Game.getGame().client.sendMoveObject(name, (int) p.getX()-x, (int) p.getY()-y);
	        dragged = false;
	    }

	    private void doPop(MouseEvent e){
	        PopUpMenu menu = new PopUpMenu((CardStack) moveComponent);
	        menu.show(e.getComponent(), e.getX(), e.getY());
	    }
		@Override
		public void mouseDragged(MouseEvent e) {
			if (dragged) {
				//moveComponent.setBounds(moveComponent.getX()+e.getX()-x,moveComponent.getY()+e.getY()-y, moveComponent.getWidth(), moveComponent.getHeight());
				Point p = SwingUtilities.convertPoint(thisStack, e.getX(), e.getY(), Game.getGame().cardtable);
		        Game.getGame().client.sendMoveObject(name, (int) p.getX()-x, (int) p.getY()-y);
			}
		}
		
		public void mouseMoved(MouseEvent e) {	
		}
		public void mouseClicked(MouseEvent e) {
		}
		public void mouseEntered(MouseEvent e) {
		}
		public void mouseExited(MouseEvent e) {
		}
	}

	class StackActionListener implements ActionListener {
		CardStack mySource;
		
		public StackActionListener(CardStack pCS){
			mySource = pCS;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			if (((JMenuItem)e.getSource()).getText().equals("Draw")) {
				//System.out.println(mySource.draw());
				Game.getGame().client.sendDrawCard(name);
			}
			else if (((JMenuItem)e.getSource()).getText().equals("Flip")) {
				//mySource.switchRevealed();
				Game.getGame().client.sendFlip(name);
			}
			else if(((JMenuItem)e.getSource()).getText().equals("Shuffle")) {
				//mySource.shuffle();
				Game.getGame().client.sendShuffle(name);
			}
			else if(((JMenuItem)e.getSource()).getText().equals("Reveal")) {
				String retV = JOptionPane.showInputDialog("Reveal how many cards:");
				try{
					Game.getGame().client.sendReveal(name, Integer.parseInt(retV));
				}catch(NumberFormatException nFE){
					fireEvent(new ActionEvent(this,42,"That's no number, you scum!"));
				}
			}
		}
		
	}
	
	

}
